import base64
import datetime
import hashlib
import json
import os
import random
import time
import traceback
import zipfile

import numpy
import requests
from loguru import logger


class YouZan:

    def __init__(self, playwright, host, app_id):
        self.app_id = app_id
        self.host = host
        self.playwright = playwright
        self.secret = "P4I8HCOP1OP7432XZVKR2MPJKKVQ7J"
        self.context = None
        self.browser = None
        self.page = None
        self.index_url = "https://account.youzan.com/login"
        self.tj_account = self.get_tujian_account()

    def login(self, account_obj):
        logger.info("操作有赞账号信息：【{}】店铺名：【{}】".format(account_obj['userName'], account_obj['account']))
        browser = self.playwright.chromium.launch(headless=False)
        self.context = browser.new_context(accept_downloads=True)
        self.page = self.context.new_page()
        self.page.goto(self.index_url, timeout=60000)
        self.wait(self.page)
        time.sleep(5)
        logger.info("点击使用账号登录...")
        self.page.click('span.login-qrcode__title__right')
        time.sleep(2)
        logger.info("点击密码登录....")
        self.page.click('span.js-tab-password-login')
        logger.info("输入手机号....")
        self.page.fill('input[class="zent-input"]', account_obj['userName'])
        logger.info("输入密码...")
        self.page.fill('input[name="password"]', account_obj['pwd'])
        time.sleep(1)
        logger.info("勾选协议...")
        self.page.query_selector_all('input[type="checkbox"]')[-1].click()
        time.sleep(1)
        logger.info("点击登录...")
        self.page.click('button[type="submit"]')
        time.sleep(5)
        logger.info("等待验证码出现...")
        for try_number in range(20):
            if self.page.query_selector('div.title:visible') is not None:
                logger.info("验证码出现，开始滑动验证码...")
                break
            else:
                time.sleep(1)
        self.slide_code()
        # 处理短信验证码
        self.set_sms_code()
        time.sleep(5)
        if self.page.query_selector('div[class="btn"]') is not None and self.page.query_selector(
                'div[class="btn"]').is_visible():
            logger.info("点击登录...")
            self.page.click('div[class="btn"]')
            self.wait(self.page)
            time.sleep(10)
            self.close_tips()
        else:
            logger.info("登录失败...")

    def download_fund_flow(self, store, yesterday):
        self.page.goto('https://www.youzan.com/v4/assets/bill?from=PC-SHARED-NAV')
        logger.info('跳转到对账单下载')
        # self.page.click('span.zp-nav-name:text-is("资产")')
        # self.page.click('//span[text()="对账单下载"]')
        self.wait(self.page)
        time.sleep(5)
        logger.info("找到昨天的账单...")
        month_text = self.page.query_selector('span.zent-datepicker-trigger-input').text_content()
        if not yesterday.startswith(month_text):
            self.page.query_selector('div.zent-datepicker-trigger').click()
            time.sleep(1)
            month = str(int(yesterday.split('-')[1]))
            with self.page.expect_response('https://www.youzan.com/v4/assets/api/bill/list?*') as res:
                self.page.query_selector(f'div.zent-datepicker-panel >> div.zent-datepicker-cell-inner:has-text("{month}月")').click()
            response = res.value
        tr_list = self.page.query_selector_all('tr.zent-grid-tr')
        tr_index = 0
        found = False
        for tr in tr_list:
            if found:
                break
            td_list = tr.query_selector_all('td')
            if len(td_list) == 0:
                continue
            for td in td_list:
                if td.text_content() == yesterday:
                    found = True
                    break
            tr_index += 1
        if not found:
            raise Exception(f"找不到【{yesterday}】的账单")
        tr_list[tr_index].query_selector('//span[text()="下载报表"]').click()
        time.sleep(20)
        logger.info("等待报表下载的页面弹出...")
        for i in range(60):
            if self.page.query_selector('//span[text()="报表下载"]') is not None and self.page.query_selector(
                    '//span[text()="报表下载"]').is_visible():
                logger.info("报表下载的页面弹出，开始下载...")
                break
            else:
                time.sleep(1)
        for i in range(120):
            try:
                if self.page.query_selector('div.cells').query_selector('div').query_selector(
                        '//span[text()="下载报表"]') is not None and \
                        self.page.query_selector('div.cells').query_selector('div').query_selector(
                            '//span[text()="下载报表"]').is_visible():
                    with self.page.expect_download() as download_info:
                        self.page.query_selector('div.cells').query_selector('div').query_selector(
                            '//span[text()="下载报表"]').click()
                    download_obj = download_info.value
                    file_dir = os.path.join(os.getcwd(), "temp")
                    if not os.path.exists(file_dir):
                        os.mkdir(file_dir)
                    file_path = os.path.join(file_dir, "{}-{}-{}-{}.zip".format("有赞资金流水", store['platformStoreName'], yesterday.replace('-', ''), datetime.datetime.now().strftime("%Y%m%d%H%M%S")))
                    if os.path.exists(file_path):
                        os.remove(file_path)
                    download_obj.save_as(file_path)
                    # 等待文件下载完成
                    for j in range(60):
                        if os.path.exists(file_path):
                            break
                        time.sleep(1)
                    logger.info("文件已经下载：【{}】".format(file_path))
                    logger.info('正在解压...')
                    detail_file_path = self.unzip(file_path)
                    self.page.query_selector('button.zent-dialog-r-close').click()
                    return detail_file_path
                else:
                    time.sleep(1)
            except:
                logger.info(traceback.format_exc())
                time.sleep(1)
        raise Exception("下载失败")

    def get_fund_flow_count(self, period):
        self.page.goto('https://www.youzan.com/v4/assets/bill?from=PC-SHARED-NAV')
        logger.info('跳转到对账单下载')
        # self.page.click('span.zp-nav-name:text-is("资产")')
        # self.page.click('//span[text()="对账单下载"]')
        self.wait(self.page)
        time.sleep(5)
        self.page.query_selector('div.zent-tabs-tab:has-text("月汇总")').click()
        time.sleep(3)
        year = period.split('-')[0]
        logger.info('选择年份...')
        self.page.query_selector('div.zent-select-v2:visible').click(timeout=3000)
        time.sleep(1)
        with self.page.expect_response('https://www.youzan.com/v4/assets/api/bill/list?*') as res:
            self.page.query_selector(f'div.zent-select-v2-option[title="{year}"]:visible').click(timeout=3000)
        response = res.value
        if response.status == 200:
            filter_list = list(filter(lambda x: x['date'] == period, response.json()['data']))
            if len(filter_list) > 0:
                return int(filter_list[0]['incomeCount'])+int(filter_list[0]['outcomeCount'])
            else:
                return 0
        else:
            logger.info('查询失败')


    # 输入手机验证码
    def set_sms_code(self):
        if self.page.query_selector('div.js-get-identify-code:visible') is not None:
            logger.info('出现手机验证码...')
            self.page.query_selector('div.js-get-identify-code:visible').click()
            for i in range(20):
                if self.page.query_selector('div.title:visible') is not None:
                    logger.info("验证码出现，开始滑动验证码...")
                    break
                else:
                    time.sleep(1)
            self.slide_code()
            time.sleep(10)
            sms_text = self.get_sms_code()
            if '有赞' not in sms_text:
                logger.info('获取验证码失败')
                raise Exception('获取验证码失败')
            # 验证码示例：【有赞】您的验证码：123456，请勿泄露给其他人
            sms_code = sms_text.split('：')[1].split('，')[0]
            self.page.query_selector('input[name="captcha"]').fill(sms_code)
            time.sleep(1)
            self.page.query_selector('div.captcha-dialog-footer >> button:has-text("确定")').click()

    def unzip(self, zip_file_path):
        file_dir = os.path.join(os.getcwd(), "有赞")
        # 删除file_dir下所有文件核文件夹
        if os.path.exists(file_dir):
            for file in os.listdir(file_dir):
                file_path = os.path.join(file_dir, file)
                if os.path.isfile(file_path):
                    os.remove(file_path)
                else:
                    for f in os.listdir(file_path):
                        sub_path = os.path.join(file_path, f)
                        os.remove(sub_path)
                    os.rmdir(file_path)
        f = zipfile.ZipFile(zip_file_path)
        for file in f.namelist():
            if '明细' in file:
                f.extract(file, file_dir)
        file_dir = os.path.join(os.getcwd(), "有赞")
        for file in os.listdir(file_dir):
            file_path = os.path.join(file_dir, file)
            if os.path.isfile(file_path):
                return file_path
            else:
                for f in os.listdir(file_path):
                    sub_path = os.path.join(file_path, f)
                    return sub_path

    def close_tips(self):
        for c in range(3):
            try:
                if self.page.query_selector('//a[text()="我知道了"]') is not None and self.page.query_selector(
                        '//a[text()="我知道了"]').is_visible():
                    logger.info("关闭提示...")
                    self.page.click('//a[text()="我知道了"]', timeout=2000)
            except:
                pass

    def slide_code(self):
        for i in range(20):
            try:
                if self.page.query_selector('#bigImg:visible') is None:
                    time.sleep(5)
                    break
                code_path = os.path.join(os.getcwd(), 'code.png')
                self.page.query_selector('#bigImg:visible').screenshot(path=code_path)
                # src = self.page.query_selector('#bigImg:visible').get_attribute('src')
                # self.save_img(code_path, src)
                time.sleep(0.5)
                dist = int(self.get_slider_xy(code_path))
                logger.info("重新滑动...【{}】".format(i + 1))
                box = self.page.query_selector('#slideIconRef:visible').bounding_box()
                track = self.get_tracks(dist - 5, 1, self.ease_out_quart)
                ix = int(box['x'] + box['width'] // 2)
                iy = int(box['y'] + box['height'] // 2)
                self.page.mouse.move(ix, iy)
                self.page.mouse.down(button='left')
                self.move_to_gap(self.page, ix, iy, track)
                time.sleep(1.5)
                logger.info("滑动完成...")
                time.sleep(5)
                if self.page.query_selector('div.title:visible') is None:
                    logger.info("滑动成功...")
                    break
            except:
                traceback.format_exc()

    def save_img(self, path, src):
        res = requests.get(src)
        with open(path, 'wb') as f:
            f.write(res.content)

    def ease_out_quart(self, x):
        return 1 - pow(1 - x, 4)

    def get_tracks(self, distance, seconds, ease_func):
        distance += 0
        tracks = [0]
        offsets = [0]
        for t in numpy.arange(0.0, seconds, 0.1):
            ease = ease_func
            offset = round(ease(t / seconds) * distance)
            tracks.append(offset - offsets[-1])
            offsets.append(offset)
        tracks.extend([-3, -2, -3, -2, -2, -2, -2, -1, -0, -1, -1, -1])
        return tracks

    def move_to_gap(self, page, ix, iy, track):
        """
        慢速移动鼠标
        :param ix:
        :param iy:
        :param track:
        :return:
        """
        f_y = [10, 12, 4, 6, 8, -4, -6, -8]
        mx = ix
        while track:
            x = track.pop(0)
            mx = mx + x
            page.mouse.move(mx, iy + random.choice(f_y))
            time.sleep(0.1)
        page.mouse.up()

    def get_slider_xy(self, code_path):
        api = 'http://api.ttshitu.com/base64'
        with open(code_path, 'rb') as f:
            base64_data = base64.b64encode(f.read())
            b642 = base64_data.decode()
        params = {"typeid": "33", "image": b642, "username": self.tj_account['tjName'],
                  "password": self.tj_account['tjPass']}
        headers = {'content-type': 'application/json;charset=UTF-8'}
        result = json.loads(requests.post(api, json=params, headers=headers).text)
        if result['success']:
            return result["data"]["result"].split(",")[0]
        else:
            raise Exception("获取验证码结果失败：【{}】".format(result['message']))

    def get_tujian_account(self):
        url = self.host + "api/v1/tjAccount/findOne?appId={}&sign={}&timestamp={}"
        timestamp = int(time.time() * 1000)
        text = "{}{}{}".format(self.app_id, timestamp, self.secret)
        sign = hashlib.md5(text.encode(encoding='UTF-8')).hexdigest()
        res = requests.get(url.format(self.app_id, sign.upper(), timestamp))
        if res.status_code == 200:
            logger.info("获取到图鉴：【{}】".format(res.json()['data']))
            return res.json()['data']
        else:
            logger.info("获取图鉴失败：【{}】".format(res.json()))
            return None

    def get_store_account(self, platform, type):
        timestamp = int(time.time() * 1000)
        text = "{}{}{}".format(self.app_id, timestamp, self.secret)
        sign = hashlib.md5(text.encode(encoding='UTF-8')).hexdigest()
        url = self.host + f"api/v1/storeAccount/getAccountList/{platform}?appId={self.app_id}&type={type}&sign={sign.upper()}&timestamp={timestamp}"
        logger.info(url)
        res = requests.get(url)
        logger.info(f'获取店铺账号结果：{res.text}')
        if res.status_code == 200:
            return res.json()['data']

    def wait(self, page, seconds=1):
        try:
            page.wait_for_load_state(state="domcontentloaded")
            time.sleep(seconds)
        except:
            traceback.format_exc()

    def close(self):
        try:
            if self.page:
                self.page.close()
        except:
            pass
        try:
            if self.browser:
                self.browser.close()
        except:
            pass
        try:
            if self.context:
                self.context.close()
        except:
            pass

    # 获取手机验证码
    def get_sms_code(self):
        for t in range(3):
            try:
                res = requests.get('http://1.95.166.150:8997/api/v1/sms/info/18664342053')
                logger.info(f'获取手机验证码结果：{res.text}')
                sms_text = res.json()['data']
                if sms_text is not None and '有赞' in sms_text:
                    return sms_text
                time.sleep(3)
            except:
                logger.info(traceback.format_exc())
        return ''

    def upload_file(self, api_url, coding_param, file_list):
        try:
            timestamp = int(time.time() * 1000)
            text = "{}{}{}".format(self.app_id, timestamp, self.secret)
            sign = hashlib.md5(text.encode(encoding='UTF-8')).hexdigest()
            logger.info("正在上传文件到服务器")
            url = self.host + api_url + f"?appId={self.app_id}&sign={sign.upper()}&timestamp={timestamp}"
            files = []
            for file in file_list:
                files.append(('file', (open(file, 'rb'))))
            logger.info(f'上传文件到服务器url：{url}')
            response = requests.request("POST", url, files=files, headers={'codingParam': coding_param}, timeout=180)
            logger.info('上传文件到服务器结果：' + response.text)
        except:
            logger.info(traceback.format_exc())

    def encode_param(self, data_list):
        timestamp = int(time.time() * 1000)
        text = "{}{}{}".format(self.app_id, timestamp, self.secret)
        sign = hashlib.md5(text.encode(encoding='UTF-8')).hexdigest()
        url = self.host + f"api/v1/storeAccount/codingParams?appId={self.app_id}&sign={sign.upper()}&timestamp={timestamp}"
        res = requests.post(url, data=json.dumps(data_list), headers={'content-type': 'application/json'})
        if res.status_code == 200:
            return res.json()['data']